﻿using Microsoft.AspNetCore.Mvc.ApplicationParts;
using MyFramework.Web.Api;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;

namespace MyFramework.Web.Api
{
    /// <summary>
    /// 插件管理
    /// </summary>
    public class MyPluginManager
    {
        private readonly ApplicationPartManager _applicationPartManager;
        /// <summary>
        /// 
        /// </summary>
        /// <param name="applicationPartManager"></param>
        public MyPluginManager(ApplicationPartManager applicationPartManager
            //, IHostingEnvironment env
            )
        {
            _applicationPartManager = applicationPartManager;
            // _hostingEnvironment = env;
        }
        /// <summary>
        /// 加载插件程序集
        /// </summary>
        /// <param name="pluginName">插件名称</param>
        public void Load(string pluginName)
        {
            // 此插件的 加载上下文
            var context = new MyAssemblyLoadContext();

            #region 加载插件主dll
            // 插件的主dll, 不包括插件项目引用的dll
            string pluginMainDllFilePath = Path.Combine(MyPluginPathProvider.Plugins, pluginName, $"{pluginName}.dll");
            Assembly pluginMainAssembly;
            using (var fs = new FileStream(pluginMainDllFilePath, FileMode.Open))
            {
                // 使用此方法, 就不会导致dll被锁定
                pluginMainAssembly = context.LoadFromStream(fs);
                AssemblyPart assemblyPart = new AssemblyPart(pluginMainAssembly);
                // 加载其中的控制器
                try
                {
                    ApplicationPart last = _applicationPartManager.ApplicationParts.FirstOrDefault(m =>
                    {
                        return m.Name == assemblyPart.Name;
                    }
                    );
                    _applicationPartManager.ApplicationParts.Remove(last);
                }
                catch (Exception ex)
                {

                }
                _applicationPartManager.ApplicationParts.Add(assemblyPart);
                // Notify change
                MyActionDescriptorChangeProvider.Instance.HasChanged = true;
                // TokenSource 为 null
                // 注意: 在程序刚启动时, 未抵达 Controller 时不会触发 IActionDescriptorChangeProvider.GetChangeToken(), 也就会导致 TokenSource 为 null, 在启动时，同时在启动时，插件Controller.Action和主程序一起被添加，此时无需通知改变
                if (MyActionDescriptorChangeProvider.Instance.TokenSource != null)
                {
                    // 只有在插件列表控制启用，禁用时才需通知改变
                    MyActionDescriptorChangeProvider.Instance.TokenSource.Cancel();
                }
            }
            #endregion


            // TODO: 跳过不需要加载的 dll, eg: ASP.NET Core Shared Framework, 主程序中已有dll
            string[] skipDlls = new string[] { "MyFramework.Core.dll" };

            #region 加载插件引用的dll
            // 加载插件引用的dll
            // eg: xxx/Plugins/HelloWorld
            string pluginDirPath = Path.Combine(MyPluginPathProvider.Plugins, pluginName);
            var pluginDir = new DirectoryInfo(pluginDirPath);
            // 插件引用的所有dll (排除 主dll 和 skipDlls )
            // 注意: 主程序中已有dll 必须跳过, 应为这些默认Context中已经加载, 而如果插件Context再次加载, 则认为这两个是不同Assembly, 导致其中的Type转换失败
            // 这里简单来说，意思就是当在一个自定义LoadContext中加载程序集的时候，如果找不到这个程序集，程序会自动去默认LoadContext中查找，如果默认LoadContext中都找不到，就会返回null。
            // 这里我突然想到会不会是因为DemoPlugin1、DemoPlugin2以及主站点的AssemblyLoadContext都加载了Mystique.Core.dll程序集的缘故，虽然他们加载的是同一个程序集，但是因为LoadContext不同，所以系统认为它是2个程序集。
            // 参考： https://www.cnblogs.com/lwqlun/p/12930713.html
            var allReferenceFileInfos = pluginDir.GetFiles("*.dll").Where(p =>
            p.Name != $"{pluginName}.dll"
            &&
            !skipDlls.Contains(p.Name));
            foreach (FileInfo file in allReferenceFileInfos)
            {
                using (var sr = new StreamReader(file.OpenRead()))
                {
                    context.LoadFromStream(sr.BaseStream);
                }
            }
            #endregion
            //context.Unload();
            // 这个插件加载上下文 放入 集合中
            MyPluginsLoadContexts.Load(pluginName, context);
        }
        /// <summary>
        /// 卸载插件程序集
        /// </summary>
        /// <param name="pluginName">插件名称</param>
        public void Unload(string pluginName)
        {
            try
            {
                ApplicationPart last = _applicationPartManager.ApplicationParts.FirstOrDefault(m =>
                {
                    return m.Name == pluginName;
                }
                );
                _applicationPartManager.ApplicationParts.Remove(last);
                // Notify change
                MyActionDescriptorChangeProvider.Instance.HasChanged = true;
                // TokenSource 为 null
                // 注意: 在程序刚启动时, 未抵达 Controller 时不会触发 IActionDescriptorChangeProvider.GetChangeToken(), 也就会导致 TokenSource 为 null, 在启动时，同时在启动时，插件Controller.Action和主程序一起被添加，此时无需通知改变
                if (MyActionDescriptorChangeProvider.Instance.TokenSource != null)
                {
                    // 只有在插件列表控制启用，禁用时才需通知改变
                    MyActionDescriptorChangeProvider.Instance.TokenSource.Cancel();
                }
            }
            catch (Exception ex)
            {

            }
            MyPluginsLoadContexts.Unload(pluginName);
            GC.Collect();
            GC.WaitForPendingFinalizers();
        }
    }
}
